package scanner

import (
	"errors"
	"learning/gooop/spring/autogen/common"
	"learning/gooop/spring/autogen/domain"
	"regexp"
	"strings"
)

type IAnnotationScanner interface {
	ScanAnnotations(s *domain.StructInfo)
}

type tAnnotationScanner int

func (me *tAnnotationScanner) ScanAnnotations(s *domain.StructInfo) {
	me.scanStructAnnotation(s)
	me.scanFieldAnnotation(s)
	me.scanMethodAnnotation(s)
}

func (me *tAnnotationScanner) scanStructAnnotation(s *domain.StructInfo) {
	for i := s.LineNO - 1; i >= 0; i-- {
		if !me.matchAnnotation(s, i) {
			break
		}

		code := s.CodeFile.RawLines[i]
		e, a := me.parseAnnotation(code)
		if e != nil {
			panic(e)
		}
		s.AppendAnnotation(a)
	}
}

func (me *tAnnotationScanner) scanFieldAnnotation(s *domain.StructInfo) {
	for _, fld := range s.Fields {
		for i := fld.LineNO - 1; i >= 0; i-- {
			if !me.matchAnnotation(s, i) {
				break
			}

			code := s.CodeFile.RawLines[i]
			e, a := me.parseAnnotation(code)
			if e != nil {
				panic(e)
			}
			fld.AppendAnnotation(a)
		}
	}
}

func (me *tAnnotationScanner) scanMethodAnnotation(s *domain.StructInfo) {
	for _, method := range s.Methods {
		for i := method.LineNO - 1; i >= 0; i-- {
			if !me.matchAnnotation(s, i) {
				break
			}

			code := s.CodeFile.RawLines[i]
			e, a := me.parseAnnotation(code)
			if e != nil {
				panic(e)
			}
			method.AppendAnnotation(a)
		}
	}
}

func (me *tAnnotationScanner) matchAnnotation(s *domain.StructInfo, lineNO int) bool {
	line := s.CodeFile.RawLines[lineNO]
	return gAnnotationStartRegexp.MatchString(line)
}

func (me *tAnnotationScanner) parseAnnotation(line string) (error, *domain.AnnotationInfo) {
	ss := gAnnotationStartRegexp.FindStringSubmatch(line)
	if len(ss) <= 0 {
		return nil, nil
	}
	a := domain.NewAnnotationInfo()

	// name
	declare := ss[0]
	a.Name = ss[1]

	// properties
	t := line[len(declare):]
	for {
		// space*
		b1, s1 := common.Tokens.MatchSpaces(t)
		if b1 {
			t = t[len(s1):]
		}

		// key
		b2, s2 := common.Tokens.MatchIdentifier(t)
		if !b2 {
			break
		}
		t = t[len(s2):]

		// =
		b31, s31 := common.Tokens.MatchSpaces(t)
		if b31 {
			t = t[len(s31):]
		}
		b32 := common.Tokens.MatchString(t, "=")
		if !b32 {
			return errors.New("expecting ="), nil
		} else {
			t = t[1:]
		}
		b33, s33 := common.Tokens.MatchSpaces(t)
		if b33 {
			t = t[len(s33):]
		}

		// value
		b4, s4, i4 := me.parsePropertyValue(t)
		if !b4 {
			return errors.New("expecting attribute value"), nil
		} else {
			t = t[i4:]
			a.AppendAttribute(s2, s4)
		}
	}

	return nil, a
}

func (me *tAnnotationScanner) parsePropertyValue(s string) (bool, string, int) {
	// quoted string by ""
	b2, s2 := common.Tokens.MatchRegexp(s, `^"((\\")|[^"])*"`)
	if b2 {
		return true, me.removeDoubleQuote(s2), len(s2)
	}

	// quoted string by ``
	b3, s3 := common.Tokens.MatchRegexp(s, "^`[^`]+`")
	if b3 {
		return true, s3[1 : len(s3)-1], len(s3)
	}

	// simple string
	b4, s4 := common.Tokens.MatchRegexp(s, `^\S+`)
	if b4 {
		return true, s4, len(s4)
	}

	return false, "", 0
}

func (me *tAnnotationScanner) removeDoubleQuote(s string) string {
	s = s[1 : len(s)-1]
	arrSpecialChars := [][]string{
		{`\r`, "\r"},
		{`\n`, "\n"},
		{`\t`, "\t"},
		{`\"`, "\""},
		{`\\`, "\\"},
		{`\v`, "\v"},
	}

	for _, it := range arrSpecialChars {
		s = strings.ReplaceAll(s, it[0], it[1])
	}
	return s
}

var gAnnotationStartRegexp = regexp.MustCompile(`^//\s*@(\w+)\s*`)

var DefaultAnnotationScanner = new(tAnnotationScanner)
